#region (c) 2014 Nguyễn, M. Hải  - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, Nguyễn, M. Hải ( www.Minh-Hai.com ). All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 08-Sep-12 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.Generic;

using Thn;
#endregion

namespace Thn.Presentation.Controls
{
    /// <summary>
    /// A special label that supports subscript and superscript.
    /// Example: (H2CO3)2 can be described as (H_2_CO_3_)^2^
    /// </summary>
    [ContentProperty("Text")]
#if DEBUG || TESTING
    public
#else
    public
#endif
 class MathLabel : StackPanel
    {
        #region Fields
        const char SuperscriptSeparator = '^';
        const char SubscriptSeparator = '_';
        #endregion

        #region Properties

        #region Text
        /// <summary>
        /// Gets or sets the expression as plain text.
        /// </summary>
        [DefaultValue("")]
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary>
        /// Backing dependency property for Text.
        /// </summary>
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
            "Text"
            , typeof(string)
            , typeof(MathLabel)
            , new PropertyMetadata("", TextPropertyChanged)
            );

        /// <summary>
        /// Invoked when value of property Text is changed.
        /// </summary>
        protected virtual void OnTextChanged(DependencyPropertyChangedEventArgs e)
        {
            BuildContent((string)e.NewValue);
        }

        /// <summary>
        /// Static handler for listening to changes of TextProperty.
        /// </summary>
        private static void TextPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            MathLabel obj = (MathLabel)target;

            //update target object
            obj.OnTextChanged(e);
        }
        #endregion

        #region Subscript Font Size
        /// <summary>
        /// Gets or sets size of subscript element.
        /// </summary>
        [DefaultValue(10.0)]
        public double SubscriptFontSize
        {
            get { return (double)GetValue(SubscriptFontSizeProperty); }
            set { SetValue(SubscriptFontSizeProperty, value); }
        }

        /// <summary>
        /// Backing dependency property for SubscriptFontSize.
        /// </summary>
        public static readonly DependencyProperty SubscriptFontSizeProperty =
            DependencyProperty.Register(
            "SubscriptFontSize"
            , typeof(double)
            , typeof(MathLabel)
            , new PropertyMetadata(10.0, SubscriptFontSizePropertyChanged)
            );

        /// <summary>
        /// Invoked when value of property SubscriptFontSize is changed.
        /// </summary>
        protected virtual void OnSubscriptFontSizeChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        /// <summary>
        /// Static handler for listening to changes of SubscriptFontSizeProperty.
        /// </summary>
        private static void SubscriptFontSizePropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            MathLabel obj = (MathLabel)target;

            //update target object
            obj.OnSubscriptFontSizeChanged(e);
        }
        #endregion

        #region Superscript Font Size
        /// <summary>
        /// Gets or sets size of superscript element.
        /// </summary>
        [DefaultValue(10.0)]
        public double SuperscriptFontSize
        {
            get { return (double)GetValue(SuperscriptFontSizeProperty); }
            set { SetValue(SuperscriptFontSizeProperty, value); }
        }

        /// <summary>
        /// Backing dependency property for SuperscriptFontSize.
        /// </summary>
        public static readonly DependencyProperty SuperscriptFontSizeProperty =
            DependencyProperty.Register(
            "SuperscriptFontSize"
            , typeof(double)
            , typeof(MathLabel)
            , new PropertyMetadata(10.0, SuperscriptFontSizePropertyChanged)
            );

        /// <summary>
        /// Invoked when value of property SuperscriptFontSize is changed.
        /// </summary>
        protected virtual void OnSuperscriptFontSizeChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        /// <summary>
        /// Static handler for listening to changes of SuperscriptFontSizeProperty.
        /// </summary>
        private static void SuperscriptFontSizePropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            MathLabel obj = (MathLabel)target;

            //update target object
            obj.OnSuperscriptFontSizeChanged(e);
        }
        #endregion

        #region Subscript Vertical Offset
        /// <summary>
        /// Gets or sets vertical offset for subscript element.
        /// </summary>
        [DefaultValue(6.0)]
        public double SubscriptVerticalOffset
        {
            get { return (double)GetValue(SubscriptVerticalOffsetProperty); }
            set { SetValue(SubscriptVerticalOffsetProperty, value); }
        }

        /// <summary>
        /// Backing dependency property for SubscriptVerticalOffset.
        /// </summary>
        public static readonly DependencyProperty SubscriptVerticalOffsetProperty =
            DependencyProperty.Register(
            "SubscriptVerticalOffset"
            , typeof(double)
            , typeof(MathLabel)
            , new PropertyMetadata(6.0, SubscriptVerticalOffsetPropertyChanged)
            );

        /// <summary>
        /// Invoked when value of property SubscriptVerticalOffset is changed.
        /// </summary>
        protected virtual void OnSubscriptVerticalOffsetChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        /// <summary>
        /// Static handler for listening to changes of SubscriptVerticalOffsetProperty.
        /// </summary>
        private static void SubscriptVerticalOffsetPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            MathLabel obj = (MathLabel)target;

            //update target object
            obj.OnSubscriptVerticalOffsetChanged(e);
        }
        #endregion

        #region Superscript Vertical Offset
        /// <summary>
        /// Gets or sets vertical offset for superscript element.
        /// </summary>
        [DefaultValue(-3.0)]
        public double SuperscriptVerticalOffset
        {
            get { return (double)GetValue(SuperscriptVerticalOffsetProperty); }
            set { SetValue(SuperscriptVerticalOffsetProperty, value); }
        }

        /// <summary>
        /// Backing dependency property for SuperscriptVerticalOffset.
        /// </summary>
        public static readonly DependencyProperty SuperscriptVerticalOffsetProperty =
            DependencyProperty.Register(
            "SuperscriptVerticalOffset"
            , typeof(double)
            , typeof(MathLabel)
            , new PropertyMetadata(-3.0, SuperscriptVerticalOffsetPropertyChanged)
            );

        /// <summary>
        /// Invoked when value of property SuperscriptVerticalOffset is changed.
        /// </summary>
        protected virtual void OnSuperscriptVerticalOffsetChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        /// <summary>
        /// Static handler for listening to changes of SuperscriptVerticalOffsetProperty.
        /// </summary>
        private static void SuperscriptVerticalOffsetPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            MathLabel obj = (MathLabel)target;

            //update target object
            obj.OnSuperscriptVerticalOffsetChanged(e);
        }
        #endregion

        #region FontWeight
        private FontWeight mFontWeight = FontWeights.Normal;
        /// <summary>
        /// Gets or sets description.
        /// </summary>
        public FontWeight FontWeight
        {
            get { return mFontWeight; }
            set
            {
                mFontWeight = value;
                BuildContent(this.Text);
            }
        }
        #endregion

        #endregion

        #region Build Content
        void BuildContent(string express)
        {
            Children.Clear();
            if (!string.IsNullOrEmpty(express))
            {
                StringBuilder sb = new StringBuilder();
                ParseState state = ParseState.PlainText;
                this.Children.Clear();
                char[] chars = express.ToCharArray();
                int length = chars.Length;
                int count = 0;
                for (int i = 0; i < length; i++)
                {
                    char c = chars[i];
                    if (c == SuperscriptSeparator)
                    {
                        #region Found superscript marker

                        switch (state)
                        {
                            case ParseState.PlainText:
                                //found new start maker

                                //build existing if available
                                if (sb.Length > 0) AppendItemFromState(sb, state);

                                //set new state
                                state = ParseState.ParsingSuperscript;
                                break;
                            case ParseState.ParsingSubscript:
                                //weird, I don't support this
                                throw new Exception("Found a subscript marker after a superscript marker");
                                break;
                            case ParseState.ParsingSuperscript:
                                //found closing marker -> build item
                                AppendItemFromState(sb, state);

                                //set new state
                                state = ParseState.PlainText;
                                break;
                            default:
                                throw new NotSupportedException();
                                break;
                        }

                        #endregion
                    }
                    else if (c == SubscriptSeparator)
                    {
                        #region Found subscript marker

                        switch (state)
                        {
                            case ParseState.PlainText:
                                //found new start maker

                                //build existing if available
                                if (sb.Length > 0) AppendItemFromState(sb, state);

                                //set new state
                                state = ParseState.ParsingSubscript;
                                break;
                            case ParseState.ParsingSuperscript:
                                //weird, I don't support this
                                throw new Exception("Found a superscript marker after a subscript marker");
                                break;
                            case ParseState.ParsingSubscript:
                                //found closing marker -> build item
                                AppendItemFromState(sb, state);

                                //set new state
                                state = ParseState.PlainText;
                                break;
                            default:
                                throw new NotSupportedException();
                                break;
                        }

                        #endregion
                    }
                    else
                    {
                        #region Plain text

                        sb.Append(c);
                        count++;

                        #endregion
                    }
                }

                //build existing if available
                if (sb.Length > 0) AppendItemFromState(sb, state);
            }
            #region Defensive tracing
            else
            {
                //null text, skip
            }
            #endregion
        }
        #endregion

        #region Append Item From State
        void AppendItemFromState(StringBuilder sb, ParseState state)
        {
            string text = sb.ToString();            
            UIElement item;
            switch (state)
            {
                case ParseState.PlainText:
                    item = BuildNormalItem(text);
                    Children.Add(item);
                    break;
                case ParseState.ParsingSubscript:
                    item = BuildSubscriptItem(text);
                    Children.Add(item);
                    break;
                case ParseState.ParsingSuperscript:
                    item = BuildSuperscriptItem(text);
                    Children.Add(item);
                    break;
                default:
                    throw new NotSupportedException();
                    break;
            }
            sb.Clear();
        }
        #endregion

        #region Build Subscript Item
        UIElement BuildSubscriptItem(string text)
        {
            TextBlock result = new TextBlock();
            result.Text = text;
            result.TextWrapping = TextWrapping.Wrap;
            result.FontWeight = mFontWeight;
            result.Margin = new Thickness()
            {
                Top = this.SubscriptVerticalOffset
            };
            result.FontSize = this.SubscriptFontSize;
            return result;
        }
        #endregion

        #region Build Superscript Item
        UIElement BuildSuperscriptItem(string text)
        {
            TextBlock result = new TextBlock();
            result.Text = text;
            result.TextWrapping = TextWrapping.Wrap;
            result.FontWeight = mFontWeight;
            result.Margin = new Thickness()
            {
                Top = this.SuperscriptVerticalOffset
            };
            result.FontSize = this.SuperscriptFontSize;
            return result;
        }
        #endregion

        #region Build Normal Item
        UIElement BuildNormalItem(string text)
        {
            TextBlock result = new TextBlock();
            result.TextWrapping = TextWrapping.Wrap;
            result.FontWeight = mFontWeight;
            result.Text = text;
            return result;
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public MathLabel()
        {
            this.Orientation = System.Windows.Controls.Orientation.Horizontal;
            this.Margin = new Thickness(5, 0, 5, 0);
            this.VerticalAlignment = System.Windows.VerticalAlignment.Center;
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public MathLabel(string text)
            : this()
        {
            this.Text = text;
        }
        #endregion

        #region Inner class
        enum ParseState
        {
            PlainText,
            ParsingSubscript,
            ParsingSuperscript
        }
        #endregion
    }
}
